﻿using System;
using System.Collections.Generic;
using System.Globalization;
using Windows.UI.Xaml.Controls;

namespace Ebt.Mvvm
{
    /// <summary>
    /// Deserialized <see cref="Frame"/> navigation stack.
    /// <seealso cref="Frame.GetNavigationState"/>
    /// </summary>
    internal class NavigationStack
    {
        public int CurrentIndex { get; set; }

        public IList<NavigationStackItem> Items { get; set; }
    }

    internal class NavigationStackItem
    {
        public string PageTypeName { get; set; }

        public object Parameter { get; set; }
    }

    internal static class NavigationStackParser
    {
        private const int SerializationVersion = 1;

        /// <summary>
        /// Deserializes the <see cref="Frame"/> navigation stack.
        /// <seealso cref="Frame.GetNavigationState"/>
        /// </summary>
        public static NavigationStack Parse(string state)
        {
            int len = state.Length;
            int p = 0;

            var version = ReadNumber(state, ref p);
            if (version != SerializationVersion)
                throw new FormatException("Unknown navigation state format.");

            var count = ReadNumber(state, ref p);

            var stack = new NavigationStack
            {
                Items = new List<NavigationStackItem>(count),
                CurrentIndex = ReadNumber(state, ref p)
            };

            for (int i = 0; i < count && p < len; i++)
            {
                var pageTypeLength = ReadNumber(state, ref p);
                if (pageTypeLength == 0)
                    throw new FormatException("Invalid navigation state format.");

                var item = new NavigationStackItem();

                item.PageTypeName = ReadSubstring(state, ref p, pageTypeLength);
                int paramTypeId = ReadNumber(state, ref p);
                
                if(paramTypeId == 0)
                    continue;

                Type paramType = ResolveType(paramTypeId);
                var paramLength = ReadNumber(state, ref p);
                string paramStr = paramLength == 0 ? string.Empty : ReadSubstring(state, ref p, paramLength);
                item.Parameter = Convert.ChangeType(paramStr, paramType, CultureInfo.InvariantCulture);
            }

            return stack;
        }

        private static Type ResolveType(int paramTypeId)
        {
            // TODO These are the only types I was able to reverse engineer.
            switch (paramTypeId)
            {
                case 0: return null;
                case 1: return typeof(byte);
                case 2: return typeof(short);
                case 3: return typeof(ushort);
                case 4: return typeof(int);
                case 5: return typeof(uint);
                case 6: return typeof(long);
                case 7: return typeof(ulong);
                case 8: return typeof(float);
                case 9: return typeof(double);
                case 10: return typeof(char);
                case 11: return typeof(bool);
                case 12: return typeof(string);
                case 16: return typeof(Guid);
                default: return null;
            }
        }

        private static string ReadSubstring(string str, ref int startIndex, int length)
        {
            var s = str.Substring(startIndex, length);
            startIndex += length + 1;
            return s;
        }

        private static int ReadNumber(string str, ref int startIndex)
        {
            var val = ReadToComma(str, ref startIndex);

            if (val == null)
                throw new FormatException();

            int n = int.Parse(val, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);

            return n;
        }

        private static string ReadToComma(string str, ref int startIndex)
        {
            int comma = str.IndexOf(',', startIndex);
            if (comma < 0)
                comma = str.Length;

            var s = str.Substring(startIndex, comma - startIndex);

            startIndex = comma + 1;

            return s;
        }
    }
}
